package com.pxample.pemo.controller;

import com.alibaba.fastjson.JSONObject;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.protobuf.ByteString;
import com.pxample.api.CityScore;
import com.pxample.api.cityscoreGrpc;
import com.pxample.pemo.service.ScoreCalculatorService;
import lombok.extern.slf4j.Slf4j;
import net.devh.boot.grpc.client.inject.GrpcClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.multipart.MultipartFile;
import reactor.core.publisher.Mono;
import org.springframework.web.reactive.function.server.ServerRequest;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;

@RestController
public class ScoreCalculatorController extends BaseController{

    private static final Logger log = LoggerFactory.getLogger(ScoreCalculatorController.class);

    @Autowired
    ScoreCalculatorService scoreCalculatorService;

    @Autowired
    ObjectMapper objectMapper;

    @GrpcClient("cityscoreclient")
    private cityscoreGrpc.cityscoreStub asyncStub;

    @GrpcClient("cityscoreclient")
    private cityscoreGrpc.cityscoreBlockingStub stub;

    AtomicInteger count = new AtomicInteger(0);



    @GetMapping("/cal-score/{citycode}")
    public Object calculateCityScore(@PathVariable Integer citycode) {
        CityScore.CityScoreRequest cityScoreRequest
                = CityScore.CityScoreRequest.newBuilder()
                .setCityCode(citycode)
                .build();
        try {


            CityScore.CityScoreResponse sync = scoreCalculatorService.sync(cityScoreRequest);

            return sync.toString();

          /*  return Mono.create(e->{
                CompletableFuture<CityScore.CityScoreResponse> async = scoreCalculatorService.async(cityScoreRequest);
                Mono<CityScore.CityScoreResponse> cityScoreResponseMono = Mono.fromFuture(scoreCalculatorService.async(cityScoreRequest));
                 cityScoreResponseMono.map(cityScoreResponse -> {
                    JSONObject param = new JSONObject();
                    param.put("city", cityScoreResponse.getCityScore());
                    return param;
                });
            });*/

         /*   asyncStub.calculateCityScore(cityScoreRequest, new StreamObserver<CityScore.CityScoreResponse>() {
                @Override
                public void onNext(CityScore.CityScoreResponse cityScoreResponse) {
                    System.err.println(count.incrementAndGet());
                }

                @Override
                public void onError(Throwable throwable) {
                    System.out.println(2222);
                }

                @Override
                public void onCompleted() {

                }
            });*/


//                  .onErrorResume(ex -> Mono.error(new RuntimeException("Failed to fetch data from gRPC service", ex)));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    @PostMapping("/upload")
    public Object upload(@RequestParam(name = "file")MultipartFile file) throws IOException {
        CityScore.FileUploadRequest fileUploadRequest = CityScore.FileUploadRequest
                .newBuilder()
                .setContent(ByteString.copyFrom(file.getBytes()))
                .setFilename(file.getOriginalFilename())
                .build();
        CityScore.FileUploadResponse upload = stub.upload(fileUploadRequest);
        return upload;
    }



    @PostMapping("/uploadFile")
    public JSONObject uploadFile(@RequestParam(name = "file")MultipartFile file) throws IOException {
        System.out.println(file.getSize() +".......................");
        JSONObject param = new JSONObject();
        param.put("url", "http://127.0.0.1:38443");
        return param;
    }


    @PostMapping("/cal-score")
    public Mono<String> post(@RequestBody String body) {
        CityScore.CityScoreRequest cityScoreRequest
                = CityScore.CityScoreRequest.newBuilder()
                .setCityCode(266)
                .build();

        try {
                return Mono.create(monoSink -> {
                    CompletableFuture<CityScore.CityScoreResponse> future = scoreCalculatorService.async(cityScoreRequest);
                    try {
                        CityScore.CityScoreResponse response = future.get();
                        monoSink.success(response.getCityScore() + "");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (ExecutionException e) {
                        e.printStackTrace();
                    }
                });

//            CompletableFuture<CityScore.CityScoreResponse> async = scoreCalculatorService.async(cityScoreRequest);
//            CityScore.CityScoreResponse response = async.get();
//            return response.getCityScore() + "";
//               System.out.println("count:" + count.incrementAndGet());
//                Mono<CityScore.CityScoreResponse> cityScoreResponseMono = Mono.fromFuture(scoreCalculatorService.async(cityScoreRequest));
//               return cityScoreResponseMono.map(cityScoreResponse -> cityScoreResponse.getCityScore() + "");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    @PostMapping("/test/mono")
    public Mono<JSONObject> mono(@RequestBody String body) {
        CityScore.CityScoreRequest cityScoreRequest
                = CityScore.CityScoreRequest.newBuilder()
                .setCityCode(266)
                .build();
        System.out.println(Thread.currentThread().getName());
        try {
            return Mono.fromFuture(()->scoreCalculatorService.asyncMono(cityScoreRequest));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @PostMapping("/test/future")
    public JSONObject postFuture(@RequestBody String body) {
        CityScore.CityScoreRequest cityScoreRequest
                = CityScore.CityScoreRequest.newBuilder()
                .setCityCode(266)
                .build();
        JSONObject params = new JSONObject();
        try {
            ListenableFuture<CityScore.CityScoreResponse> future = scoreCalculatorService.future(cityScoreRequest);
            CityScore.CityScoreResponse response = future.get();
            params.put("status", 0);
            params.put("data", response.getCityScore());
            return params;
        } catch (Exception e) {
            params.put("status", 1);
            params.put("data", e.getMessage());
        }
        return params;
    }


    @PostMapping("/grpc/client/sync")
    public JSONObject postSync1(@RequestBody JSONObject data) {
        long begin = System.currentTimeMillis();
        Integer server_sync = data.getInteger("server_sync");
        Integer sleep = data.getInteger("sleep");
        CityScore.CityScoreRequest request
                = CityScore.CityScoreRequest.newBuilder()
                .setSleep(sleep)
                .setServerSync(server_sync)
                .setCityCode(266)
                .build();
        JSONObject params = new JSONObject();
        try {
            CityScore.CityScoreResponse response = scoreCalculatorService.sync(request);
            params.put("status", 0);
            params.put("data", response.getCityScore());
            log.info("/grpc/client/sync to finished.....|{}|{}|{}", sleep, server_sync, System.currentTimeMillis() - begin);
            return params;
        } catch (Exception e) {
            params.put("status", 1);
            params.put("data", e.getMessage());
            log.error("/grpc/client/sync to error.....|{}|{}|", sleep, server_sync, e);
        }
        return params;
    }

    @PostMapping("/grpc/client/async")
    public Mono<JSONObject> grpcAsync(@RequestBody JSONObject data) {
        long begin = System.currentTimeMillis();
        Integer server_sync = data.getInteger("server_sync");
        Integer sleep = data.getInteger("sleep");
        CityScore.CityScoreRequest cityScoreRequest
                = CityScore.CityScoreRequest.newBuilder()
                .setSleep(sleep)
                .setServerSync(server_sync)
                .setCityCode(266)
                .build();
        try {
            return Mono.fromFuture(()->{
                CompletableFuture<JSONObject> future = scoreCalculatorService.asyncMono(cityScoreRequest);
                log.info("/grpc/client/async to finished.....|{}|{}|{}", sleep, server_sync, System.currentTimeMillis() - begin);
                return future;
            });
        } catch (Exception e) {
            log.error("/grpc/client/async to error.....|{}|{}", sleep, server_sync, e);
        }
        return Mono.empty();
    }

    @Autowired
    Executor executor;

    @PostMapping("/grpc/client/async/servlet")
    public DeferredResult<JSONObject> servlet(@RequestBody JSONObject data) {
        long begin = System.currentTimeMillis();
        Integer server_sync = data.getInteger("server_sync");
        Integer sleep = data.getInteger("sleep");
        CityScore.CityScoreRequest cityScoreRequest
                = CityScore.CityScoreRequest.newBuilder()
                .setSleep(sleep)
                .setServerSync(server_sync)
                .setCityCode(266)
                .build();
        DeferredResult<JSONObject> result = new DeferredResult<>();

        scoreCalculatorService.asyncMono(cityScoreRequest).whenComplete((response, throwable)->{
            if (null != throwable){
                result.setResult(response);
                log.error("/grpc/client/async/servlet to error.....|{}|{}|{}", sleep, server_sync, response);
            }else {
                result.setResult(response);
            }
            log.info("/grpc/client/async/servlet to finished.....|{}|{}|{}", sleep, server_sync, System.currentTimeMillis() - begin);
        });
        return result;
    }


    @PostMapping("/grpc/client/sync/servlet")
    public DeferredResult<JSONObject> servletAsync(@RequestBody JSONObject data) {
        long begin = System.currentTimeMillis();
        Integer server_sync = data.getInteger("server_sync");
        Integer sleep = data.getInteger("sleep");
        CityScore.CityScoreRequest cityScoreRequest
                = CityScore.CityScoreRequest.newBuilder()
                .setSleep(sleep)
                .setServerSync(server_sync)
                .setCityCode(266)
                .build();
        DeferredResult<JSONObject> result = new DeferredResult<>();
        CompletableFuture.supplyAsync(()-> scoreCalculatorService.sync(cityScoreRequest), executor).whenComplete((response, throwable)->{
            if (null != throwable){
                JSONObject params = new JSONObject();
                params.put("status", 1);
                params.put("data", response.getCityScore());
                result.setResult(params);
                log.error("/grpc/client/sync/servlet to error.....|{}|{}|{}", sleep, server_sync, params);
            }else {
                JSONObject params = new JSONObject();
                params.put("status", 0);
                params.put("data", response.getCityScore());
                result.setResult(params);
            }
            log.info("/grpc/client/sync/servlet to finished.....|{}|{}|{}", sleep, server_sync, System.currentTimeMillis() - begin);
        });
        return result;
    }



    @PostMapping("/cal-score/sub")
    public Mono<Object> post1(ServerRequest request, @RequestBody String body) {
        CityScore.CityScoreRequest cityScoreRequest
                = CityScore.CityScoreRequest.newBuilder()
                .setCityCode(266)
                .build();
            Mono<CityScore.CityScoreRequest> monoRequest = request.bodyToMono(CityScore.CityScoreRequest.class);
            return monoRequest.flatMap(req->{
                Mono<CityScore.CityScoreResponse> helloReplyMono = Mono.fromCallable(() -> {
                    CityScore.CityScoreResponse reply = scoreCalculatorService.sync(req);
                    return reply;
                });
                return helloReplyMono;
            });
    }


    @GetMapping("/asyncHello")
    public CompletableFuture<String> asyncHello() {
        return CompletableFuture.supplyAsync(() -> {
            // 模拟耗时操作
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            return "Hello from async endpoint!";
        });
    }





    private Map<String, Object> of(Object value){
        Map<String, Object> map = new HashMap<>();
        map.put("city", value);
        return map;
    }
}
